(*
  Copyright (c) 2009 Barry Schwartz

  Permission is hereby granted, free of charge, to any person
  obtaining a copy of this software and associated documentation
  files (the "Software"), to deal in the Software without
  restriction, including without limitation the rights to use,
  copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the
  Software is furnished to do so, subject to the following
  conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  OTHER DEALINGS IN THE SOFTWARE.
*)

(*-----------------------------------------------------------------------*)

open XNum
open Substitute
open GlyphMetric
open FontForge
open FontMetric
open OpenType
open UniTools

(*-----------------------------------------------------------------------*)

let num_of_int64 n = num_of_float (Int64.to_float n)
let num_0x100000 = num_of_int 0x100000
let unfix_num n = div_num (num_of_int64 n) num_0x100000

let get_italic_correction scale ff_glyph =
  match FFglyph.it_correct ff_glyph with
    | None -> num_zero
    | Some d -> mult_num scale (num_of_int d)

(*-----------------------------------------------------------------------------------
(* The old version of this function -- left here a little while for
   reference. FIX: Remove this code. *)
let make_glyph_metric params extra_kern scale glyph =
  let adv = FFglyph.h_advance glyph in
  let (x0, y0, x1, y1) = FFglyph.bbox glyph in
  let width = x1 - x0 in
  let height = y1 - y0 in
  let h_off = -(FFglyph.lsb glyph) in
  let v_off = y1 in
  let left_bound = max 0 (-h_off) in
  let right_bound = max 0 (width - h_off - adv) in
  let l = mult_num scale (num_of_int left_bound) in
  let r = mult_num scale (num_of_int right_bound) in
  let user_kern_info =
    try
      (* FIX: Glyph protrusion should be specified by glyph _name_
         rather than by glyph _index_. Specifying by glyph index is
         practically useless: the glyph indices can change whenever
         the font is edited. *)
      let ki = List.assoc (FFglyph.originalgid glyph) extra_kern in
        { ki_after_space = mult_num params.flp_size ki.ki_after_space;
          ki_before_space = mult_num params.flp_size ki.ki_before_space;
          ki_after_margin = mult_num params.flp_size ki.ki_after_margin;
          ki_before_margin = mult_num params.flp_size ki.ki_before_margin;
          ki_after_foreign = mult_num params.flp_size ki.ki_after_foreign;
          ki_before_foreign = mult_num params.flp_size ki.ki_before_foreign }
    with
      | Not_found -> zero_kern_info
  in

  (* If these values are zero we do not need to allocate a new
     structure. *)
  let kern_info =
    if left_bound <> 0 || right_bound <> 0 then
      { user_kern_info with
          ki_after_foreign = add_num l user_kern_info.ki_after_foreign;
          ki_before_foreign = add_num r user_kern_info.ki_before_foreign }
    else
      user_kern_info
  in
    { gm_width =
        add_num (mult_num scale (num_of_int adv))
          (mult_num (mult_num num_two params.flp_size)
             params.flp_letter_spacing);
      gm_height = mult_num scale (num_of_int v_off);
      gm_depth = mult_num scale (num_of_int (height - v_off));
      gm_italic = get_italic_correction scale glyph;
      gm_extra = GXI_Normal;
      gm_extra_kern = kern_info }
------------------------------------------------------------------------------*)

let make_glyph_metric params extra_kern scale glyph =
  let adv = FFglyph.h_advance glyph in
  let (_x0, y0, _x1, y1) = FFglyph.bbox glyph in
  let height = y1 - y0 in
  let v_off = y1 in
  let kern_info =
    try
      (* FIX: Glyph protrusion should be specified by glyph _name_
         rather than by glyph _index_. Specifying by glyph index is
         practically useless: the glyph indices can change whenever
         the font is edited. *)
      let ki = List.assoc (FFglyph.originalgid glyph) extra_kern in
        { ki_after_space = mult_num params.flp_size ki.ki_after_space;
          ki_before_space = mult_num params.flp_size ki.ki_before_space;
          ki_after_margin = mult_num params.flp_size ki.ki_after_margin;
          ki_before_margin = mult_num params.flp_size ki.ki_before_margin;
          ki_after_foreign = mult_num params.flp_size ki.ki_after_foreign;
          ki_before_foreign = mult_num params.flp_size ki.ki_before_foreign }
    with
      | Not_found -> zero_kern_info
  in
    { gm_width =
        add_num (mult_num scale (num_of_int adv))
          (mult_num (mult_num num_two params.flp_size)
             params.flp_letter_spacing);
      gm_height = mult_num scale (num_of_int v_off);
      gm_depth = mult_num scale (num_of_int (height - v_off));
      gm_italic = get_italic_correction scale glyph;
      gm_extra = GXI_Normal;
      gm_extra_kern = kern_info }

let get_glyph_metric params extra_kern scale glyphs =
  Array.map (make_glyph_metric params extra_kern scale) glyphs

let builtin_encoding font char =
  Simple (FFglyph.originalgid (FFfont.enc_glyph font char))
    
let builtin_decoding glyphs glyph =
  let lookup i =
    if Array.length glyphs <= i then
      []
    else
      let enc = FFglyph.glyph_enc glyphs.(i) in
        if enc = -1 then
          []
        else
          [enc]
  in
  let rec decode glyph =
    match glyph with
      | Undef -> []
      | Simple g -> lookup g
      | Raised (g, _) -> lookup g
      | Accent (a, g) -> (lookup a) @ (lookup g)
      | Sequence gs -> List.concat (List.map lookup gs)
      | Extendable (t, m, b, _) -> (decode t) @ ((decode m) @ (decode b))
  in
    Array.of_list (decode glyph)

(*-----------------------------------------------------------------------*)

let read_ot ff_font file name params =

  let tex_param = FFfont.tex_param ff_font in
  let glyphs = FFfont.glyphs ff_font in
  let first_glyph = 0 in
  let last_glyph = Array.length glyphs - 1 in
  let asc = FFfont.ascent ff_font in
  let desc = FFfont.descent ff_font in
  let em = FFfont.em ff_font in

  let size = params.flp_size in
  let scale = div_num size (num_of_int em) in
  let design_size = mult_num scale (num_of_int (asc - desc)) in

  let scale_to_size n =
    mult_num size (unfix_num n)
  in

  let (enc, dec) =
    match params.flp_encoding with
      | [|  |] -> (builtin_encoding ff_font, builtin_decoding glyphs)
      | m -> (Encodings.charmap_encoding (Encodings.fake_encoding m), Encodings.array_decoding m)
  in

  let extra_kern =
    let lookup_name name =
      try FFglyph.originalgid (FFfont.str_glyph ff_font name) - 1
      with | Not_found -> -1
    in
    let lookup_char x =
      match enc x with
        | Simple g -> g
        | Raised (g, _) -> g
        | _ -> (-1)
    in
      List.map
        (fun (g, k) -> ((glyph_spec_to_index lookup_char lookup_name g), k))
        params.flp_extra_kern
  in

  let glyph_metric = get_glyph_metric params extra_kern scale glyphs in

  let glyph_width_height unicode default =
    try
      let glyph = FFfont.enc_glyph ff_font unicode in
      let gid = FFglyph.originalgid glyph in
        (glyph_metric.(gid).gm_width, glyph_metric.(gid).gm_height)
    with | Not_found -> default
  in
  let space =
    if tex_param.(1) <> Int64.zero then
      scale_to_size tex_param.(1)
    else
      let (sp, _) = glyph_width_height 0x20 (div_num size (num_of_int 3), num_zero) in
        sp
  in
  let x_height =
    if tex_param.(4) <> Int64.zero then
      scale_to_size tex_param.(4)
    else
      let (_, x_ht) = glyph_width_height 0x78 (num_zero, div_num size (num_of_int 2)) in (* 0x78 = 'x' *)
        x_ht
  in
  let quad =
    if tex_param.(5) <> Int64.zero then
      scale_to_size tex_param.(5)
    else
      let (qu, _) = glyph_width_height 0x4D (size, num_zero) in (* 0x4D = 'M' *)
        qu
  in
  let hyphen_glyph =
    match params.flp_hyphen_glyph with
      | Undef -> builtin_encoding ff_font 45
      | h -> h
  in

  let font_type =
    if FFfont.quadratic ff_font then
      TrueType
    else
      OpenTypeCFF
  in

  let restrict_langsys = make_langsys_restricter ff_font in

  let gsub_markers = MarkerRepository.create () in
  let get_gsub_marker script lang =
    let (restricted_script, restricted_lang) = restrict_langsys script lang in
      try
        MarkerRepository.get gsub_markers restricted_script restricted_lang
      with
        | Not_found ->
            MarkerRepository.put_gsub gsub_markers ff_font restricted_script restricted_lang;
            MarkerRepository.get gsub_markers restricted_script restricted_lang
  in

  let gpos_markers = MarkerRepository.create () in
  let get_gpos_marker script lang =
    let (restricted_script, restricted_lang) = restrict_langsys script lang in
      try
        MarkerRepository.get gpos_markers restricted_script restricted_lang
      with
        | Not_found ->
            MarkerRepository.put_gpos gpos_markers ff_font restricted_script restricted_lang;
            MarkerRepository.get gpos_markers restricted_script restricted_lang
  in
  let composer font script features =
    Substitute.ot_composer scale get_gsub_marker get_gpos_marker font script features
  in
  let get_glyph_index name =
    FFglyph.originalgid (FFfont.str_glyph ff_font name)
  in
  let space_stretch =
    if tex_param.(2) <> Int64.zero then
      scale_to_size tex_param.(2)
    else
      div_num space (num_of_int 2)
  in
  let space_shrink =
    if tex_param.(3) <> Int64.zero then
      scale_to_size tex_param.(3)
    else
      div_num space (num_of_int 3)
  in

  let get_glyph_name i =
    FFglyph.glyph_str glyphs.(i)
  in
    { id = new_fontmetric_id ();
      name = name;
      ps_name = FFfont.ps_name ff_font;
      file_name = file;
      font_type = font_type;
      first_glyph = first_glyph;
      last_glyph = last_glyph;
      design_size = design_size;
      at_size = size;
      check_sum = num_zero;
      get_glyph = enc;
      get_unicode = dec;
      get_composer = composer;
      kerning = (fun _ _c1 _c2 -> NoLigKern);
      draw_simple_glyph =
        if eq_num params.flp_letter_spacing num_zero then
          draw_simple_glyph
        else
          draw_displaced_simple_glyph
            (mult_num (mult_num scale (num_of_int em))
               params.flp_letter_spacing)
            num_zero;
      accent_base_point = accent_base_point_x_height;
      accent_attach_point = accent_attach_point_top;
      get_glyph_bitmap = (fun _ -> failwith "Bitmapping of glyphs is not supported");
      get_glyph_name = get_glyph_name;
      get_glyph_index = get_glyph_index;
      code_to_glyph_index = (fun c -> c);
      glyph_name_to_code = get_glyph_index;
      parameter = {
        hyphen_glyph = hyphen_glyph;
        skew_glyph = params.flp_skew_glyph;
        margin_glyph = Simple (last_glyph + 1);
        space_glyph = Simple (last_glyph + 2);
        foreign_glyph = Simple (last_glyph + 3);
        slant = unfix_num tex_param.(0);
        space = space;
        space_stretch = space_stretch;
        space_shrink = space_shrink;
        x_height = x_height;
        quad = quad;
        extra_space = scale_to_size tex_param.(6);
        num_shift_1 = scale_to_size tex_param.(7);
        num_shift_2 = scale_to_size tex_param.(8);
        num_shift_3 = scale_to_size tex_param.(9);
        denom_shift_1 = scale_to_size tex_param.(10);
        denom_shift_2 = scale_to_size tex_param.(11);
        super_shift_1 = scale_to_size tex_param.(12);
        super_shift_2 = scale_to_size tex_param.(13);
        super_shift_3 = scale_to_size tex_param.(14);
        sub_shift_1 = scale_to_size tex_param.(15);
        sub_shift_2 = scale_to_size tex_param.(16);
        super_drop = scale_to_size tex_param.(17);
        sub_drop = scale_to_size tex_param.(18);
        delim_1 = scale_to_size tex_param.(19);
        delim_2 = scale_to_size tex_param.(20);
        axis_height = scale_to_size tex_param.(21);
        rule_thickness = scale_to_size tex_param.(7);
        big_op_spacing_1 = scale_to_size tex_param.(8);
        big_op_spacing_2 = scale_to_size tex_param.(9);
        big_op_spacing_3 = scale_to_size tex_param.(10);
        big_op_spacing_4 = scale_to_size tex_param.(11);
        big_op_spacing_5 = scale_to_size tex_param.(12);
      };
      glyph_metric = glyph_metric;
      ff_font = Some ff_font;
    }
    
(*-----------------------------------------------------------------------*)
